/*
 www.pipoisu.com
 Copyright (c) 2011.  All rights reserved.

 File     : i2cmaster.cpp
 Author(s): Erez Raviv

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <inttypes.h>
#include <compat/twi.h>

#include "i2cmaster.h"


//F_CPU defined in Makefile

/* I2C clock in Hz */
#define SCL_CLOCK  400000L   //400khz clock


/*************************************************************************
 Initialization of the I2C bus interface. Need to be called only once
*************************************************************************/
void i2c_init(void)
{
  /* initialize TWI clock: 400 kHz clock, TWSR => prescaler = 4 */
  
  TWSR = (1<<TWPS0);                    // prescaler  = 4
  TWBR = 3;//(((F_CPU/SCL_CLOCK)-16)/2)/4;  // must be > 10 for stable operation

}/* i2c_init */


/*************************************************************************	
  Issues a start condition and sends address and transfer direction.
  return 0 = device accessible, 1= failed to access device
*************************************************************************/
unsigned char i2c_start(unsigned char address)
{
    uint8_t   twst;

        // send START condition
        TWCR = (1<<TWINT) | (1<<TWSTA) | (1<<TWEN);

        // wait until transmission completed
        while(!(TWCR & (1<<TWINT)));

        // check value of TWI Status Register. Mask prescaler bits.
        twst = TW_STATUS & 0xF8;
        if ( (twst != TW_START) && (twst != TW_REP_START)) return 1;

        // send device address
        TWDR = address;
        TWCR = (1<<TWINT) | (1<<TWEN);

        // wail until transmission completed and ACK/NACK has been received
        while(!(TWCR & (1<<TWINT)));

        // check value of TWI Status Register. Mask prescaler bits.
        twst = TW_STATUS & 0xF8;
        if ( (twst != TW_MT_SLA_ACK) && (twst != TW_MR_SLA_ACK) ) return 1;

        return 0;

}/* i2c_start */




/*************************************************************************
 Terminates the data transfer and releases the I2C bus
*************************************************************************/
void i2c_stop(void)
{
    /* send stop condition */
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
	
	// wait until stop condition is executed and bus released
	while(TWCR & (1<<TWSTO));

}/* i2c_stop */


/*************************************************************************
  Send one byte to I2C device
  
  Input:    byte to be transfered
  Return:   0 write successful 
            1 write failed
*************************************************************************/
unsigned char i2c_write( unsigned char data )
{	
    uint8_t   twst;
    
	// send data to the previously addressed device
	TWDR = data;
	TWCR = (1<<TWINT) | (1<<TWEN);

	// wait until transmission completed
	while(!(TWCR & (1<<TWINT)));

	// check value of TWI Status Register. Mask prescaler bits
	twst = TW_STATUS & 0xF8;
	if( twst != TW_MT_DATA_ACK) return 1;
	return 0;

}/* i2c_write */


/*************************************************************************
 Read one byte from the I2C device, request more data from device 
 Return:  byte read from I2C device
*************************************************************************/
unsigned char i2c_readAck(void)
{
	TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWEA);
	while(!(TWCR & (1<<TWINT)));    

    return TWDR;

}/* i2c_readAck */


/*************************************************************************
 Read one byte from the I2C device, read is followed by a stop condition 
 
 Return:  byte read from I2C device
*************************************************************************/
unsigned char i2c_readNak(void)
{
	TWCR = (1<<TWINT) | (1<<TWEN);
	while(!(TWCR & (1<<TWINT)));
	
    return TWDR;

}/* i2c_readNak */




/*************************************************************************
 Write 1 byte to addr - use wait to make sure device is ready
*************************************************************************/
void i2cWrite(uint8_t addr, uint8_t reg, uint8_t val)
{
    i2c_start(addr+I2C_WRITE);
    i2c_write(reg);
    i2c_write(val);
    i2c_stop();
}


/*************************************************************************
 Read 1 byte to addr - use wait to make sure device is ready
*************************************************************************/
uint8_t i2cRead(uint8_t addr, uint8_t reg)
{
    i2c_start(addr+I2C_WRITE);
    i2c_write(reg);
    i2c_start(addr+I2C_READ);
    uint8_t ret = i2c_readNak();
    i2c_stop();
    return ret;
}


/*************************************************************************
 Make sure device is ready
*************************************************************************/
uint8_t i2cDeviceReady(uint8_t addr)
{
    uint8_t ret = i2c_start(addr+I2C_WRITE);
    i2c_stop();
    return (ret==0);  // if ret==0 then device is ready
}


/*************************************************************************
 read 6 consecutive bytes
*************************************************************************/
void i2cRead6(uint8_t addr, uint8_t reg, uint8_t * t)
{
    i2c_start(addr+I2C_WRITE);
    i2c_write(reg);  // start from first register.
    i2c_start(addr+I2C_READ);
    t[0] = i2c_readAck();  // auto-increment read address
    t[1] = i2c_readAck();
    t[2] = i2c_readAck();
    t[3] = i2c_readAck();
    t[4] = i2c_readAck();
    t[5] = i2c_readNak();
    i2c_stop();
}
